﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Vortex.Drawing;
using System.IO;
using Vortex.Utils;
using Vortex.Drawing.Particles;
using System.Xml.Serialization;
using Vortex.Drawing.Utils;
using Vortex.Tools.FormControls;

namespace Vortex.ParticleEditor {
    public partial class PEMainForm : Form {
        readonly string WINDOW_TITLE = "Vortex2D.NET Particle Editor v0.5";

        const float RADIAN_TO_DEG = (float)(180 / Math.PI);
        const float DEG_TO_RADIAN = (float)(Math.PI / 180);
        const float RATIO_TO_PERCENT = 100f;
        const float PERCENT_TO_RATIO = 0.01f;

        Random m_Random = new Random();

        bool m_SettingChangedState;
        FileInfo m_WorkingFile;

        SingleContextDevice m_Device;
        Canvas2D m_Canvas;

        ParticleSystem m_ParticleSystem;
        ParticleEditorSettings m_EditorSettings;
        ParticleEffect m_ParticleEffect;

        ViewSettings m_ViewSettings;

        List<Bitmap> m_SpriteBitmaps = new List<Bitmap>();

        public PEMainForm() {
            InitializeComponent();
        }

        private void PEMainForm_Load(object sender, EventArgs e) {
            m_Device = new SingleContextDevice(this.TargetPanel.Handle);
            m_Canvas = m_Device.Context.Canvas;

            m_ParticleEffect = new ParticleEffect();
            m_ParticleSystem = m_ParticleEffect.CreateParticleSystem();

            m_ViewSettings = new ViewSettings();
            m_ViewSettings.ViewportWidth = TargetPanel.Width;
            m_ViewSettings.ViewportHeight = TargetPanel.Height;
            
            Reset();

            LoadSpriteGlyphs();
            UpdateControlsFromParticleFactory();
            UpdateWindowText();
        }

        void Reset() {
            m_ParticleEffect.Params = new ParticleEffectParameters();
            m_EditorSettings = new ParticleEditorSettings();
            m_ParticleSystem.Emitter.Location = new Vector2(TargetPanel.Width / 2, TargetPanel.Height / 2);
        }

        private void LoadSpriteGlyphs() {
            SpritePresetsComboBox.Items.Clear();
            for (int n = 1; n < 1000; ++n) {
                FileInfo fi = new FileInfo(string.Format("sprites\\particle{0}.png", n));
                if (fi.Exists) {
                    Bitmap bmp = new Bitmap(fi.FullName);
                    m_SpriteBitmaps.Add(bmp);
                    SpritePresetsComboBox.Items.Add(n.ToString());
                }
            }
            if (SpritePresetsComboBox.Items.Count > 0) {
                SpritePresetsComboBox.SelectedIndex = 0;
            }
        }

        private void MarkChanged(bool state) {
            if (state != m_SettingChangedState) {
                m_SettingChangedState = state;
                UpdateWindowText();
            }
        }

        private void UpdateWindowText() {
            this.Text = (null == m_WorkingFile ? "new.peff" : m_WorkingFile.Name) + (m_SettingChangedState ? "*" : "") + " - " + WINDOW_TITLE;
        }

        private Range MakeRangeValue(Vortex.Tools.FormControls.RangeControl rangeControl) {
            return new Range(rangeControl.FromValue, rangeControl.ToValue);
        }

        private Range MakeRangeValue(Vortex.Tools.FormControls.RangeControl rangeControl, float multiplicator) {
            return new Range(rangeControl.FromValue * multiplicator, rangeControl.ToValue * multiplicator);
        }

        private void TargetPanel_Paint(object sender, PaintEventArgs e) {
			m_Device.Context.Present();
        }

        private void FrameTimer_Tick(object sender, EventArgs e) {
			if (m_Device.BeginScene()) {
				m_Canvas.Clear(m_EditorSettings.BackgroundColor);

				DrawContent();

				m_Canvas.DrawRect(m_Canvas.Region, ColorU.Black);

				m_Device.EndScene();
				m_Device.Context.Present();
			}
        }

        private void DrawContent() {
            float timeDelta = 1 / (float)m_ViewSettings.FixedFPS;

            //first lets render particle system
            m_ParticleSystem.Continous = !m_ViewSettings.EmitOnClick;
            m_ParticleSystem.Update(timeDelta);

            using (m_Canvas <= m_ViewSettings.BlendingStyle) {
                m_ParticleSystem.Draw(m_Canvas, false);
            }

            //put number of rendered particles at the top
            m_Canvas.DrawText(SpriteFont.Console, new Vector2(2, 2), "Number of particles: " + m_ParticleSystem.Collection.Count, ColorU.White);
        }

        private void BackgroundColorPanel_Click(object sender, EventArgs e) {
            ColorDialog.Color = BackgroundColorPanel.BackColor;
            if (ColorDialog.ShowDialog() == DialogResult.OK) {
                m_EditorSettings.BackgroundColor = new ColorU(ColorDialog.Color);
                BackgroundColorPanel.BackColor = ColorDialog.Color;
                MarkChanged(true);
            }
        }

        private void EmitRateControl_Changed(float fromValue, float toValue) {
            m_ParticleSystem.Emitter.Rate = fromValue;
            MarkChanged(true);
        }

        private void TargetPanel_MouseDown(object sender, MouseEventArgs e) {
            if (e.Button == MouseButtons.Left) {
                m_ParticleSystem.MoveTo(e.X, e.Y);
                if (m_ViewSettings.EmitOnClick) {
                    m_ParticleSystem.Emit((int)m_ParticleEffect.Emitter.Rate);
                }
            }
        }

        private void TargetPanel_MouseMove(object sender, MouseEventArgs e) {
            if (e.Button == MouseButtons.Left) {
                m_ParticleSystem.MoveTo(e.X, e.Y);
            }
        }

        private void StartSizeRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.StartSize = new Range(fromValue, toValue);
            if (SizeLinkStatus.Checked) {
                EndSizeRangeControl.Syncronize(StartSizeRangeControl);
            }
            MarkChanged(true);
        }

        private void EndSizeRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.EndSize = new Range(fromValue, toValue);
            MarkChanged(true);
        }

        private void LifetimeRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.Lifetime = new Range(fromValue, toValue);
            MarkChanged(true);
        }

        private void SizeLinkStatus_CheckedChanged(object sender, EventArgs e) {
            EndSizeRangeControl.Enabled = !SizeLinkStatus.Checked;
            EndSizeRangeControl.Syncronize(StartSizeRangeControl);
            MarkChanged(true);
        }

        private Color PickColor(Color color) {
            ColorDialog.Color = color;
            if (ColorDialog.ShowDialog() == DialogResult.OK) {
                return ColorDialog.Color;
            } else {
                return color;
            }
        }

        private void BlendingOptions_SelectedIndexChanged(object sender, EventArgs e) {
            switch (BlendingOptions.SelectedIndex) {
                case 0:
                    m_ViewSettings.BlendingStyle = Blending.Normal;
                    break;
                case 1:
                    m_ViewSettings.BlendingStyle = Blending.Add;
                    break;
                case 2:
                    m_ViewSettings.BlendingStyle = Blending.Subtract;
                    break;
            }
            m_EditorSettings.BlendingMode = BlendingOptions.SelectedIndex;
            MarkChanged(true);
        }

        private void SpritePresetsComboBox_MeasureItem(object sender, MeasureItemEventArgs e) {
        }

        private void SpritePresetsComboBox_DrawItem(object sender, DrawItemEventArgs e) {
            e.DrawBackground();
            if (e.Index >= 0) {
                Bitmap bitmap = m_SpriteBitmaps[e.Index];
                e.Graphics.DrawImage(bitmap, new Rectangle(e.Bounds.X + 1, e.Bounds.Y + 1, 32, 32));
            }
        }

        private void SpritePresetsComboBox_SelectedIndexChanged(object sender, EventArgs e) {
            if (m_ViewSettings.ParticleTexture != null) {
                m_ViewSettings.ParticleTexture.Dispose();
            }
            int index = SpritePresetsComboBox.SelectedIndex;
            m_EditorSettings.SpriteIndex = index;
            if (index >= 0) {
                Bitmap bitmap = m_SpriteBitmaps[index];
				m_ViewSettings.ParticleTexture = new Texture(bitmap);
                m_ParticleEffect.SpriteSource = m_ViewSettings.ParticleTexture.ToSprite();
				m_ParticleSystem.Collection.Clear();
            } else {
                m_ViewSettings.ParticleTexture = null;
                m_ParticleEffect.SpriteSource = null;
            }
            MarkChanged(true);
            
        }

        private void AddCustomSpriteButton_Click(object sender, EventArgs e) {
            if (OpenImageDialog.ShowDialog() == DialogResult.OK) {
                Bitmap bmp = new Bitmap(OpenImageDialog.FileName);
                if (bmp.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb ||
                    bmp.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb ||
                    bmp.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppRgb) {
                    m_SpriteBitmaps.Add(bmp);
                    int newIndex = SpritePresetsComboBox.Items.Add("custom");
                    SpritePresetsComboBox.SelectedIndex = newIndex;
                }
                else {
                    bmp.Dispose();
                }
                
            }
        }

        private void VelocityRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.Velocity = new Range(fromValue, toValue);
            MarkChanged(true);
        }

        private void AccelerationRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.Acceleration = new Range(fromValue, toValue);
            MarkChanged(true);
        }

        private void DirectionRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.Direction = new Range(fromValue * DEG_TO_RADIAN, toValue * DEG_TO_RADIAN);
            MarkChanged(true);
        }

        private void DirectionSpreadRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.DirectionSpread = new Range(fromValue * DEG_TO_RADIAN, toValue * DEG_TO_RADIAN);
            MarkChanged(true);
        }

        private void SpinRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.Spin = new Range(fromValue * DEG_TO_RADIAN, toValue * DEG_TO_RADIAN);
            MarkChanged(true);
        }

        private void SpinAccelerationRangeContrl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.SpinAcceleration = new Range(fromValue * DEG_TO_RADIAN, toValue * DEG_TO_RADIAN);
            MarkChanged(true);
        }

        private void TangentialSpeedRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.TangentialSpeed = new Range(fromValue * DEG_TO_RADIAN, toValue * DEG_TO_RADIAN);
            MarkChanged(true);
        }

        private void TangentialAccelerationRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.TangentialAcceleration = new Range(fromValue * DEG_TO_RADIAN, toValue * DEG_TO_RADIAN);
            MarkChanged(true);
        }

        private void GravityDirectionRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.GravityDirection = new Range(fromValue * DEG_TO_RADIAN, toValue * DEG_TO_RADIAN);
            MarkChanged(true);
        }

        private void GravityRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleEffect.Params.Gravity = new Range(fromValue, toValue);
            MarkChanged(true);
        }

        private void EmitOnClickCheckBox_CheckedChanged(object sender, EventArgs e) {
            m_ViewSettings.EmitOnClick = EmitOnClickCheckBox.Checked;
        }

        private void EmitRegionRadiusRangeCntrol_Changed(float fromValue, float toValue) {
            m_ParticleSystem.Emitter.DistributionRange = new Range(fromValue, toValue);
            MarkChanged(true);
        }

        private void newToolStripMenuItem_Click(object sender, EventArgs e) {
            if (CheckUnsavedWarning()) {
                Reset();                
                UpdateControlsFromParticleFactory();
                m_WorkingFile = null;
                MarkChanged(false);
                UpdateWindowText();
            }
        }

        private bool CheckUnsavedWarning() {
            if (m_SettingChangedState) {
                DialogResult result = MessageBox.Show("There are some unsaved changes. Do you want to save them before you proceed?", "Unsaved changes", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                if (result == DialogResult.Yes) {
                    saveToolStripMenuItem_Click(null, null);
                }
                else if (result == DialogResult.Cancel) {
                    return false;
                }
            }
            return true;
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) {
            if (null != m_WorkingFile) {
                SaveFileDialog.FileName = m_WorkingFile.Name;
                SaveFileDialog.InitialDirectory = m_WorkingFile.Directory.FullName;
            }
            if (SaveFileDialog.ShowDialog() == DialogResult.OK) {
                m_WorkingFile = new FileInfo(SaveFileDialog.FileName);
                SaveToFile(m_WorkingFile.FullName);
                MarkChanged(false);
                UpdateWindowText();
            }
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e) {
            OpenFileDialog.FileName = "";

            if (CheckUnsavedWarning()) {
                if (OpenFileDialog.ShowDialog() == DialogResult.OK) {
                    LoadFromFile(OpenFileDialog.FileName);
                    UpdateControlsFromParticleFactory();
                    m_WorkingFile = new FileInfo(OpenFileDialog.FileName);
                    MarkChanged(false);
                    UpdateWindowText();
                }
            }
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e) {
            if (null == m_WorkingFile) {
                saveAsToolStripMenuItem_Click(sender, e);
            } else {
                SaveToFile(m_WorkingFile.FullName);
                MarkChanged(false);
                UpdateWindowText();
            }
        }

        private void SaveToFile(string fileName) {
            m_ParticleEffect.SaveToFile(fileName, m_EditorSettings);
        }

        private void LoadFromFile(string fileName) {
            using (FileStream file = new FileStream(fileName, FileMode.Open)) {
                XmlSerializer serializer = new XmlSerializer(typeof(ParticleEffectFile));
                ParticleEffectFile fileContent = (ParticleEffectFile)serializer.Deserialize(file);
                m_ParticleEffect.Params = fileContent.Effect;
                m_ParticleEffect.Emitter = fileContent.Emitter;
                m_EditorSettings = fileContent.EditorSettings;
                m_ParticleSystem = m_ParticleEffect.CreateParticleSystem();
            }
        }

        private void UpdateControlsFromParticleFactory() {
            //copy all settings to appropriate controls
            StartSizeRangeControl.AutoSetValues(m_ParticleEffect.Params.StartSize.From, m_ParticleEffect.Params.StartSize.To);
            EndSizeRangeControl.AutoSetValues(m_ParticleEffect.Params.EndSize.From, m_ParticleEffect.Params.EndSize.To);
            VelocityRangeControl.AutoSetValues(m_ParticleEffect.Params.Velocity.From, m_ParticleEffect.Params.Velocity.To);
            AccelerationRangeControl.AutoSetValues(m_ParticleEffect.Params.Acceleration.From, m_ParticleEffect.Params.Acceleration.To);
            DirectionRangeControl.AutoSetValues(m_ParticleEffect.Params.Direction.From * RADIAN_TO_DEG, m_ParticleEffect.Params.Direction.To * RADIAN_TO_DEG);
            DirectionSpreadRangeControl.AutoSetValues(m_ParticleEffect.Params.DirectionSpread.From * RADIAN_TO_DEG, m_ParticleEffect.Params.DirectionSpread.To * RADIAN_TO_DEG);
            SpinRangeControl.AutoSetValues(m_ParticleEffect.Params.Spin.From * RADIAN_TO_DEG, m_ParticleEffect.Params.Spin.To * RADIAN_TO_DEG);
            SpinAccelerationRangeContrl.AutoSetValues(m_ParticleEffect.Params.SpinAcceleration.From * RADIAN_TO_DEG, m_ParticleEffect.Params.SpinAcceleration.To * RADIAN_TO_DEG);
            TangentialSpeedRangeControl.AutoSetValues(m_ParticleEffect.Params.TangentialSpeed.From * RADIAN_TO_DEG, m_ParticleEffect.Params.TangentialSpeed.To * RADIAN_TO_DEG);
            TangentialAccelerationRangeControl.AutoSetValues(m_ParticleEffect.Params.TangentialAcceleration.From * RADIAN_TO_DEG, m_ParticleEffect.Params.TangentialAcceleration.To * RADIAN_TO_DEG);
            GravityDirectionRangeControl.AutoSetValues(m_ParticleEffect.Params.GravityDirection.From * RADIAN_TO_DEG, m_ParticleEffect.Params.GravityDirection.To * RADIAN_TO_DEG);
            GravityRangeControl.AutoSetValues(m_ParticleEffect.Params.Gravity.From, m_ParticleEffect.Params.Gravity.To);
            LifetimeRangeControl.AutoSetValues(m_ParticleEffect.Params.Lifetime.From, m_ParticleEffect.Params.Lifetime.To);

			GradientEditControl.Points = ConvertGradient(m_ParticleEffect.Params.ColorGradient);

            EmitRegionDirectionOffsetControl.AutoSetValues(m_ParticleSystem.Emitter.Direction * RADIAN_TO_DEG, m_ParticleSystem.Emitter.Direction * RADIAN_TO_DEG);
            pulseRangeControl.AutoSetValues(m_ParticleSystem.Emitter.PulseAmount, m_ParticleSystem.Emitter.PulseAmount);
            EmitRegionRadiusRangeCntrol.AutoSetValues(m_ParticleSystem.Emitter.DistributionRange.From, m_ParticleSystem.Emitter.DistributionRange.To);
            EmitRateControl.AutoSetValues(m_ParticleSystem.Emitter.Rate, m_ParticleSystem.Emitter.Rate);

			BackgroundColorPanel.BackColor = (Color)m_EditorSettings.BackgroundColor;

            //SpritePresetsComboBox.SelectedIndex = -1;
            if (m_EditorSettings.SpriteIndex < SpritePresetsComboBox.Items.Count) {
                SpritePresetsComboBox.SelectedIndex = m_EditorSettings.SpriteIndex;
            } else {
                SpritePresetsComboBox.SelectedIndex = 0;
            }

            EmitRegionType.SelectedIndex = (int)m_ParticleEffect.Emitter.RegionType;
            BlendingOptions.SelectedIndex = m_EditorSettings.BlendingMode;

            MarkChanged(false);
            UpdateWindowText();
        }

		/// <summary>
		/// Converts the Vortex.Drawing.Utils gradient to control gradient format
		/// </summary>
		/// <param name="colorGradient">The color gradient.</param>
		/// <returns>Converted color gradient</returns>
		private GradientStopPoint[] ConvertGradient(GradientStop[] colorGradient) {
			GradientStopPoint[] points = new GradientStopPoint[colorGradient.Length];
			for (int n = 0; n < colorGradient.Length; ++n) {
				points[n] = new GradientStopPoint(colorGradient[n].Color, colorGradient[n].Position);
			}
			return points;
		}

		/// <summary>
		/// Converts the control gradient format into Vortex.Drawing.Utils gradient.
		/// </summary>
		/// <param name="colorGradient">The color gradient.</param>
		/// <returns>Converted color gradient</returns>
		private GradientStop[] ConvertGradient(GradientStopPoint[] colorGradient) {
			GradientStop[] points = new GradientStop[colorGradient.Length];
			for (int n = 0; n < colorGradient.Length; ++n) {
				points[n] = new GradientStop(colorGradient[n].Position, colorGradient[n].Color);
			}
			return points;
		}

		private void aboutToolStripMenuItem_Click(object sender, EventArgs e) {
            AboutBox aboutBox = new AboutBox();
            aboutBox.ShowDialog();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e) {
            if (CheckUnsavedWarning()) {
                Application.Exit();
            }
        }

        private void EmitRegionType_SelectedIndexChanged(object sender, EventArgs e) {
            m_ParticleSystem.Emitter.RegionType = (EmitterRegionType)EmitRegionType.SelectedIndex;
            EmitRegionDirectionOffsetControl.Enabled = (m_ParticleSystem.Emitter.RegionType == Vortex.Drawing.Particles.EmitterRegionType.Rectangular);
        }

        private void EmitRegionDirectionOffsetControl_Changed(float fromValue, float toValue) {
            m_ParticleSystem.Emitter.Direction = fromValue * DEG_TO_RADIAN;
            MarkChanged(true);
        }

        private void pulseRangeControl_Changed(float fromValue, float toValue) {
            m_ParticleSystem.Emitter.PulseAmount = fromValue;
            MarkChanged(true);
        }

		private void gradientEditControl1_ColorPointChange(object sender, Vortex.Tools.FormControls.ColorChangeEventArgs args) {
			ColorDialog.Color = args.Color;
			if (ColorDialog.ShowDialog() == DialogResult.OK) {
				args.Color = ColorDialog.Color;
			}
		}

		private void GradientEditControl_ColorPointChange(object sender, ColorChangeEventArgs args) {
			ColorDialog.Color = args.Color;
			if (ColorDialog.ShowDialog() == DialogResult.OK) {
				args.Color = ColorDialog.Color;
			}
		}

		private void GradientEditControl_Changed(object sender, EventArgs e) {
			m_ParticleEffect.Params.ColorGradient = ConvertGradient((sender as GradientEditControl).Points);
		}

    }
}
